﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using SlimMath;

/**
 * Pickのテストは未実装
 */

namespace M3G {

    public class Group : Node {
        #region Field
        bool       lodEnabled;
        float      lodOffset;
        float      lodHysteresis;
        int        lodSelectedChild;
        float      lodBlendFactor;
        float      lodEstimated;
        #endregion

        #region Method
        public Group () {
            this.lodEnabled = false;
            this.lodOffset = 0.0f;
            this.lodHysteresis = 0.0f;
            this.lodSelectedChild = -1;
            this.lodBlendFactor = 0.0f;
            this.lodEstimated = 0.0f;
        }

        protected Group (Group group)
            : base (group) {
            this.lodEnabled = group.lodEnabled;
            this.lodOffset = group.lodOffset;
            this.lodHysteresis = group.lodHysteresis;
            this.lodSelectedChild = group.lodSelectedChild;
            this.lodBlendFactor = group.lodBlendFactor;
            this.lodEstimated = group.lodEstimated;

            this.parent = null;
        }



        public int ChildCount {
            get { return children.Count; }
        }

        public Node[] Child {
            get {
                return children.ToArray ();
            }
        }

        public void AddChild (Node child) {
            if (child == null) {
                throw new ArgumentNullException ("Child is null");
            }
            if (child == this) {
                throw new ArgumentException ("No node became child of itself");
            }
            if (child.GetType () == typeof (World)) {
                throw new ArgumentException ("World can't became child of others");
            }
            if (child.Parent != null) {
                throw new ArgumentException ("Requested node has already parent");
            }
            if (Ancestors.Contains (child) == true) {
                throw new ArgumentException ("Detect cyclic reference, child is ancestors of this Object");
            }

            child.SetParent (this);
            this.children.Add (child);
            this.children.Sort ((x, y) => (int)(x.LODResolution - y.LODResolution));
        }

        // M3G非標準の関数。便利そうだから追加したけどどうしようか?
        public static Group operator + (Group group, Node child) {
            if (group != null) {
                group.AddChild (child);
            }
            return group;
        }

        public void InsertChild (Node child, int index) {
            if (child == null) {
                throw new ArgumentNullException ("Child is null");
            }
            if (child == this) {
                throw new ArgumentException ("Any node can't became child of itself");
            }
            if (child.GetType () == typeof (World)) {
                throw new ArgumentException ("World can't became child of others");
            }
            if (child.Parent != null && child.Parent != this) {
                throw new ArgumentException ("Requested node has already parent");
            }
            // ArgumentException if child is an ancestor of this group.
            // How?
            if (index < 0 || index > ChildCount) {
                throw new ArgumentOutOfRangeException ("Index is out of range, index=" + index);
            }

            this.children.Insert (index, child);
        }

        public void RemoveChild (Node child) {
            // ArgumentException if removing child would break a connection betreen a SkinnedMesh node and any bone in its current skeleton;

            this.children.Remove (child);
        }

        public bool LODEnabled {
            get { return lodEnabled; }
        }

        public float LODHysteresis {
            get { return lodHysteresis; }
        }

        public void SetLODEnable (bool enable, float hysteresis) {
            if (hysteresis < 0 || hysteresis > 1) {
                throw new ArgumentException ("Hysteresis must be in [0,1], hys=" + hysteresis);
            }

            this.lodEnabled = enable;
            this.lodHysteresis = hysteresis;
        }



        public int LODChild {
            get {
                return lodSelectedChild;
            }
        }



        public float LODOffset {
            get { return lodOffset; }
            set { lodOffset = value; }
        }

        public float LODBlendFactor {
            get { return lodBlendFactor; }
        }

        public float LODEstimated {
            get { return lodEstimated; }
        }


        public bool Pick (uint scope, float x, float y, Camera camera, RayIntersection ri) {
            if (camera == null) {
                throw new ArgumentNullException ("Camera is null");
            }
            var modelViewInv = this.GetTransformTo (camera);
            if (modelViewInv == null) {
                throw new InvalidOperationException ("Can't reach from Camera to This");
            }
            var projInv = camera.ProjectionTransform;
            projInv.Invert ();
            modelViewInv.Invert ();

            var p1 = new float[] { 2 * x - 1, 1 - 2 * y, -1, 1 };
            var p2 = new float[] { 2 * x - 1, 1 - 2 * y, 1, 1 };
            projInv.TransformVectors (p1);
            projInv.TransformVectors (p2);
            p1.DividedByW ();
            p2.DividedByW ();
            modelViewInv.TransformVectors (p1);
            modelViewInv.TransformVectors (p2);
            Array.Resize (ref p1, 3);
            Array.Resize (ref p2, 3);

            var pos = p1;
            var dir = p1.LookTo(p2);

            return Intersect(scope, pos, dir, ri);
        }

        public bool Pick (uint scope, float ox, float oy, float oz, float dx, float dy, float dz, RayIntersection ri) {
            var pos = new float[] { ox, oy, oz };
            var dir = new float[] { dx, dy, dz };
            return Intersect(scope, pos, dir, ri);
        }

        internal override bool Intersect (uint scope, float[] pos, float[] dir, RayIntersection ri) {
            if ((scope & Scope) == 0) {
                return false;
            }

            // バウンディングボリュームの判定
            var culled = CulledByBoundingVolume (pos, dir);
            if (culled) {
                return false;
            }

            // 子ノードの探索
            return base.Intersect (scope, pos, dir, ri);
        }

        public override void Align (Node reference) {
            foreach (var child in children) {
                child.Align (reference);
            }
        }

        public override Object3D[] References {
            get {
                var q = new List<Object3D> ();
                q.AddRange (base.References);
                q.RemoveAll (x => x == null);
                return q.ToArray ();
            }
        }

        public override void SelectLOD (Camera camera, float resX, float resY) {
            if (!lodEnabled) {
                this.lodSelectedChild = -1;
                foreach (var child in children) {
                    child.SelectLOD (camera, resX, resY);
                }
                return;
            }
            if (camera == null) {
                throw new ArgumentNullException ("Camera is null");
            }
            if (resX < 0.0f || resY < 0.0f) {
                throw new ArgumentException ("Resolution X and Y must be [0,inf], resX=" + resX + ",resY=" + resY);
            }

            var modelView = GetTransformTo (camera) ?? null;
            //if (modelView_ == null) {
            //    throw new InvalidOperationException ("Transformation from this object to camera object can't be caluculated");
            //}
            //var modelView = (Transform)modelView_;
            
            var x = new SlimMath.Vector3 (modelView.Matrix[0], modelView.Matrix[4], modelView.Matrix[8]);
            var y = new SlimMath.Vector3 (modelView.Matrix[1], modelView.Matrix[5], modelView.Matrix[9]);
            var z = new SlimMath.Vector3 (modelView.Matrix[2], modelView.Matrix[6], modelView.Matrix[10]);
            var s = Mathf.Max (x.Length, y.Length, z.Length);

            var offset = new Transform ();
            offset.PostTranslate (0, 0, lodOffset);

            var modelClip = new Transform ();
            modelClip.PostMultiply (camera.ProjectionTransform);
            modelClip.PostMultiply (offset);
            modelClip.PostMultiply (modelView);
            var wclip = modelClip.Matrix[15];

            var projection = camera.ProjectionTransform;
            var px = new SlimMath.Vector3 (projection.Matrix[0], projection.Matrix[4], projection.Matrix[8]);
            var py = new SlimMath.Vector3 (projection.Matrix[1], projection.Matrix[5], projection.Matrix[9]);

            var sx = 0.5f * resX * s * px.Length / wclip;
            var sy = 0.5f * resY * s * py.Length / wclip;

            var slod = Math.Max (sx, sy);
            var index = LODResolutions.LastIndexOf (r => r <= slod);

            Console.WriteLine ("Slod = " + slod);
            this.lodEstimated = slod;

            if (index < 0) {
                this.lodSelectedChild = -1;
                this.lodBlendFactor = 0.0f;

            }
            if (index >= 0 && index < ChildCount - 1) {
                var a = LODResolutions[index];
                var b = LODResolutions[index + 1];
                var blendFactor = (slod - a) / (b - a);
                if (index == lodSelectedChild + 1 && blendFactor < lodHysteresis) {
                    index = lodSelectedChild;
                }
                this.lodSelectedChild = index;
                this.lodBlendFactor = blendFactor;
            }
            if (index == ChildCount - 1) {
                this.lodSelectedChild = index;
                this.lodBlendFactor = 0.0f;
            }

            foreach (var child in children) {
                if (index >= 0 && child == children[index]) {
                    child.RenderingEnabled = true;
                    child.SelectLOD (camera, resX, resY);
                }
                else {
                    child.RenderingEnabled = false;
                }
            }
        }

        float[] LODResolutions {
            get {
                return children.ConvertAll (x => x.LODResolution).ToArray ();
            }
        }

        public override void GenerateCollisionShape (int orientations, Boolean useExisting) {

        }

        public override Object3D Duplicate () {
            return new Group (this);
        }

   
        #endregion


    }
}
